//file:noinspection DuplicatedCode
package com.galaxis.wms


import com.querydsl.core.types.Path
import com.querydsl.sql.RelationalPathBase
import com.yvan.core.Assert
import com.yvan.core.Conv
import com.yvan.core.NamingUtils
import com.yvan.core.json.JsonWrapper
import com.yvan.data.dynamic.sql.ognl.OgnlClassResolver
import com.yvan.data.dynamic.sql.ognl.OgnlMemberAccess
import com.yvan.data.jdbc.Dao
import com.yvan.data.jdbc.DaoFactory
import com.yvan.data.jdbc.MyBatis
import com.yvan.data.jdbc.querydsl.utils.SQLClause
import com.yvan.groovy.utils.GroovyInvokeUtils
import com.yvan.model.YvanExt
import com.yvan.model.request.page.IPage
import com.yvan.model.response.Model
import com.yvan.model.response.ModelOps
import groovy.transform.Canonical
import groovy.transform.MapConstructor
import groovy.transform.ToString
import groovy.util.logging.Slf4j
import ognl.Ognl
import ognl.OgnlException
import org.apache.commons.lang3.StringUtils

import java.util.concurrent.ConcurrentHashMap
import java.util.stream.Collectors

/**
 * 作者：lizw <br/>
 * 创建时间：2022/07/08 17:21 <br/>
 */
@Slf4j
class Dev {
    public static final Map<String, Object> OGNL_CONTEXT = [:]
    private static final OgnlMemberAccess MEMBER_ACCESS = new OgnlMemberAccess()
    private static final OgnlClassResolver CLASS_RESOLVER = new OgnlClassResolver()
    private static final Map<String, Object> EXPRESSION_CACHE = new ConcurrentHashMap<>()
    private static final def OGNL_ROOT = new OgnlRoot(DaoFactory.getDao(Void))

    private static Object parseExpression(String expression) throws OgnlException {
        Object node = EXPRESSION_CACHE.get(expression)
        if (node == null) {
            node = Ognl.parseExpression(expression)
            EXPRESSION_CACHE.put(expression, node)
        }
        return node
    }

    private static Object getValue(String expression) {
        if (Objects.equals(expression, "wh_id")) {
            return 0
        }
        try {
            Map context = Ognl.createDefaultContext(OGNL_ROOT, MEMBER_ACCESS, CLASS_RESOLVER, null)
            context.putAll(OGNL_CONTEXT)
            return Ognl.getValue(parseExpression(expression), context, OGNL_ROOT)
        } catch (Throwable ignored) {
            log.warn("Error evaluating expression '{}'.", expression)
            // log.warn("Error evaluating expression '{}'. Cause: ", expression, e)
            // throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e)
            return null
        }
    }

    static queryDsl = DaoFactory.getQueryDSL()

    @SuppressWarnings('GroovyAssignabilityCheck')
    static Model batchSave(Map<String, Object> params) {
        ReqData reqData = new ReqData(params)
        String className = "com/galaxis/wms${reqData.config.path}"
        className = className.replace('/', ".").replace('\\', ".")
        // beforeSave
        if (reqData.config.dbSave != null && StringUtils.isNotBlank(Conv.asString(reqData.config.dbSave.beforeSave))) {
            GroovyInvokeUtils.invoke(className, reqData.config.dbSave.beforeSave, params)
            reqData = new ReqData(params)
        }
        long rowEffect = 0
        int i = 0       // 插入行计数
        int u = 0       // 更新行计数
        int d = 0       // 删除行计数
        String msg = "" // 异常提示文本
        // save
        if (reqData.config.dbSave != null && StringUtils.isNotBlank(Conv.asString(reqData.config.dbSave.save))) {
            GroovyInvokeUtils.invoke(className, reqData.config.dbSave.save, params)
        } else {
            Assert.isNotBlank(reqData.config.dbTableName, "dbTableName 不能为空")
            String dbTableNameSmall = NamingUtils.underline2Camel(reqData.config.dbTableName)
            String dbTableNameUp = NamingUtils.underline2Camel(reqData.config.dbTableName, false)
            Class<?> queryClass = Class.forName("com.galaxis.wms.query.Q${dbTableNameUp}")
            Assert.notNull(queryClass, "QueryDsl Q类不存在")
            RelationalPathBase<?> dslTable = queryClass.getField(dbTableNameSmall).get(null)
            Assert.notNull(dslTable, "QueryDsl Q类定义错误")
            // insert
            def insertColumns = reqData.config.columns.stream()
                    .filter { StringUtils.isNotBlank(it.dbColumn) && !reqData.config.saveRowValues.containsKey(it.dbColumn) && !Conv.asBoolean(it.dbInsertDisable) }
                    .collect(Collectors.toList())
            if (reqData.newRows != null && !reqData.newRows.isEmpty()) {
                Assert.notEmpty(insertColumns, "未配置 columns")
                def insert = queryDsl.insert(dslTable)
                for (Map<String, Object> row : reqData.newRows) {
                    int setCount = 0
                    for (def insertColumn : insertColumns) {
                        def value = row[insertColumn.dataIndex]
                        if (StringUtils.isNotBlank(insertColumn.serverCreateValue)) {
                            value = getValue(insertColumn.serverCreateValue)
                        }
                        if(insertColumn.jsonMode) {
                            JsonWrapper jsonWrapper = new JsonWrapper(value)
                            value = jsonWrapper.toString()
                        }
                        if (value == null) {
                            value = reqData.config.saveRowValues[insertColumn.dbColumn]
                        }
                        String field = NamingUtils.underline2Camel(insertColumn.dbColumn)
                        Path<?> dslPath = dslTable[field]
                        if (value != null) {
                            insert.setx(dslPath, value)
                            setCount++
                        }
                    }
                    if (reqData.config.saveRowValues != null && !reqData.config.saveRowValues.isEmpty()) {
                        reqData.config.saveRowValues.forEach { key, value ->
                            String field = NamingUtils.underline2Camel(key)
                            Path<?> dslPath = dslTable[field]
                            insert.setx(dslPath, value)
                            setCount++
                        }
                    }
                    if (setCount > 0) {
                        insert.addBatch()
                    }
                }
                if (insert.batchCount > 0) {
                    i = insert.execute()
                    msg += ",插入${i}行"
                    rowEffect += i
                }
            }
            // delete
            def delColumns = reqData.config.columns.stream()
                    .filter { StringUtils.isNotBlank(it.dbColumn) && Conv.asBoolean(it.dbIsDeleteKey) }
                    .collect(Collectors.toList())
            if (reqData.removeRecords != null && !reqData.removeRecords.isEmpty()) {
                Assert.notEmpty(delColumns, "未配置 dbIsDeleteKey")
                def delete = queryDsl.delete(dslTable)
                for (Map<String, Object> row : reqData.removeRecords) {
                    for (def delColumn : delColumns) {
                        def value = row._origin[delColumn.dataIndex]
                        String field = NamingUtils.underline2Camel(delColumn.dbColumn)
                        Path<?> dslPath = dslTable[field]
                        if (value != null) {
                            delete.where(dslPath.eq(SQLClause.getFieldValue(dslPath, value)))
                        } else {
                            delete.where(dslPath.isNull())
                        }
                    }
                    delete.addBatch()
                }
                if (delete.batchCount > 0) {
                    d = delete.execute()
                    msg += ",删除${d}行"
                    rowEffect += d
                }
            }

            // update
            def updateColumns = reqData.config.columns.stream()
                    .filter { StringUtils.isNotBlank(it.dbColumn) && !Conv.asBoolean(it.dbUpdateDisable) }
                    .collect(Collectors.toList())
            def updateWhereColumns = reqData.config.columns.stream()
                    .filter { StringUtils.isNotBlank(it.dbColumn) && Conv.asBoolean(it.dbIsUpdateKey) }
                    .collect(Collectors.toList())
            if (reqData.modifyRows != null && !reqData.modifyRows.isEmpty() && !updateColumns.isEmpty()) {
                Assert.notEmpty(updateColumns, "未配置 dbUpdateEnable")
                Assert.notEmpty(updateWhereColumns, "未配置 dbIsUpdateKey")
                def update = queryDsl.update(dslTable)
                for (Map<String, Object> row : reqData.modifyRows) {
                    // set
                    int updateCount = 0
                    for (def updateColumn : updateColumns) {
                        def value = row[updateColumn.dataIndex]
                        if(updateColumn.jsonMode) {
                            JsonWrapper jsonWrapper = new JsonWrapper(value)
                            value = jsonWrapper.toString()
                        }
                        def origin_value = row._origin[updateColumn.dbColumn]
                        if (Objects.equals(value, origin_value)) {
                            continue
                        }
                        if (value == null) {
                            value = reqData.config.saveRowValues[updateColumn.dbColumn]
                        }
                        String field = NamingUtils.underline2Camel(updateColumn.dbColumn)
                        Path<?> dslPath = dslTable[field]
                        update.setx(dslPath, value)
                        updateCount++
                    }
                    if (updateCount <= 0) {
                        continue
                    }
                    // where
                    for (def whereColumn : updateWhereColumns) {
                        def value = row._origin[whereColumn.dataIndex]
                        String field = NamingUtils.underline2Camel(whereColumn.dbColumn)
                        Path<?> dslPath = dslTable[field]
                        if (value != null) {
                            update.where(dslPath.eq(SQLClause.getFieldValue(dslPath, value)))
                        } else {
                            update.where(dslPath.isNull())
                        }
                    }
                    update.addBatch()
                }
                if (update.batchCount > 0) {
                    u = update.execute()
                    msg += ",更新${u}行"
                    rowEffect += u
                }
            }

            // 返回前端
            if (rowEffect <= 0) {
                return ModelOps.newSuccess().setMsg("没有要更新的内容")
            }
        }
        // afterSave
        if (reqData.config.dbSave != null && StringUtils.isNotBlank(Conv.asString(reqData.config.dbSave.afterSave))) {
            GroovyInvokeUtils.invoke(className, reqData.config.dbSave.afterSave, params)
        }
        return Model.newSuccess([
                update   : u,
                insert   : i,
                delete   : d,
                rowEffect: rowEffect
        ]).setMsg("保存成功" + msg)
    }

    @SuppressWarnings('GroovyAssignabilityCheck')
    static Object executeSQL(Map<String, Object> params) {
        String path = Conv.asString(params.path)
        String sqlId = Conv.asString(params.sqlId)
        Map<String, Object> parameter = params != null ? params : new HashMap<>()
        boolean pagination = Conv.asBoolean(params.pagination)
        Assert.isNotBlank(path, "path 不能为空")
        Assert.isNotBlank(sqlId, "sqlId 不能为空")
        if (path.startsWith("/")) {
            path = path.substring(1)
        }
        // 优先加载 groovy
        try {
            String className = path
            def idx = className.lastIndexOf('.')
            if (idx > 0) {
                className = className.substring(0, idx)
            }
            className = className.replace('/', ".").replace('\\', ".")
            return GroovyInvokeUtils.invoke(className, sqlId, params)
        } catch (Exception ignored) {
        }
        final MyBatis myBatis = DaoFactory.getMyBatis("${path}.xml")
        // 添加自定义变量
        if (!parameter.containsKey('wh_id')) {
            def whId = 0
            parameter.put('wh_id', whId)
        }
        if (!parameter.containsKey('whId')) {
            def whId = 0
            parameter.put('whId', whId)
        }
        Model result
        if (pagination) {
            IPage<Map<String, Object>> page = myBatis.queryByPage(sqlId, YvanExt.getQueryByPage(), parameter)
            result = Model.newSuccess(page)
        } else {
            List<Map<String, Object>> list = myBatis.queryMany(sqlId, parameter)
            result = Model.newSuccess(list)
        }
        return result
    }
}

@MapConstructor
@Canonical
@ToString
class ReqData {
    List<Map<String, Object>> newRows
    /**
     * 包含 “_origin”
     */
    List<Map<String, Object>> modifyRows
    /**
     * 包含 “_origin”
     */
    List<Map<String, Object>> removeRecords

    Config config

    @MapConstructor
    @Canonical
    @ToString
    static class Config {
        List<Column> columns
        String dbTableName
        String path
        Map<String, Object> saveRowValues
        DbSave dbSave
    }

    @MapConstructor
    @Canonical
    @ToString
    static class Column {
        String dataIndex
        String header
        String dbColumn
        String serverCreateValue
        boolean jsonMode
        boolean dbIsDeleteKey
        boolean dbIsUpdateKey
        boolean dbUpdateDisable
        boolean dbInsertDisable
    }

    static class DbSave {
        String beforeSave
        String save
        String afterSave
    }
}

class OgnlRoot {
    private final Dao dao

    OgnlRoot(Dao dao) {
        this.dao = dao
    }

    Object snow_id() {
        return dao.snowId()
    }

    Object snow_id(String seqName) {
        return dao.snowId(seqName)
    }

    Object next_code(String idName) {
        return dao.nextCode(idName)
    }

    Object wh_id() {
        WhIdHolder.getWhId()
    }
}